package serialization;

import org.jdom.Document;
import org.jdom.Element;

import org.jdom.output.XMLOutputter;

import java.lang.reflect.*;

import java.util.IdentityHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.LinkedList;

import java.util.Properties;

import mopex.Mopex;


public class Driver {

//start extract serializeObject

    public static Document serializeObject( Object source )
	throws Exception
    {
	return serializeHelper( source,
				new Document( new Element("serialized") ),
				new IdentityHashMap() );
    }

    private static Document serializeHelper( Object source,
					     Document target, 
					     Map table )
	throws Exception
    {
	String id = Integer.toString( table.size() );               //#1
	table.put( source, id );                                    //#1
	Class sourceclass = source.getClass();                      //#1

	Element oElt = new Element("object");                       //#2
	oElt.setAttribute( "class", sourceclass.getName() );        //#2
	oElt.setAttribute( "id", id );                              //#2
	target.getRootElement().addContent(oElt);                   //#2

	if ( !sourceclass.isArray() ) {                             //#3
	    Field[] fields = Mopex.getInstanceVariables(sourceclass);       //#4
	    for (int i=0; i<fields.length; i++) {                           //#4

		if ( !Modifier.isPublic(fields[i].getModifiers()) )         //#5
		    fields[i].setAccessible(true);                          //#5

		Element fElt = new Element("field");                        //#6
		fElt.setAttribute( "name", fields[i].getName() );           //#6
		Class declClass = fields[i].getDeclaringClass();            //#6
		fElt.setAttribute( "declaringclass",                        //#6
				   declClass.getName() );                   //#6
	                                                                    //#6
		Class fieldtype = fields[i].getType();                      //#6
		Object child = fields[i].get(source);                       //#6
		                                                            //#6
		if ( Modifier.isTransient(fields[i].getModifiers()) ){      //#6
		    child = null;                                           //#6
		}                                                           //#6
		fElt.addContent( serializeVariable( fieldtype, child,       //#6
						    target, table));        //#6
		                                                            //#6
		oElt.addContent(fElt);                                      //#6
	    }
	}
	else {
	    Class componentType = sourceclass.getComponentType();       //#7
	                                                                //#7
	    int length = Array.getLength(source);                       //#7
	    oElt.setAttribute( "length", Integer.toString(length) );    //#7
	                                                                //#7
	    for (int i=0; i<length; i++) {                              //#7
		oElt.addContent( serializeVariable( componentType,      //#7
						    Array.get(source,i),//#7
						    target,             //#7
						    table ) );          //#7
	    }                                                           //#7
	}
	return target;
    }
//stop extract serializeObject


//start extract serializeVariable

    private static Element serializeVariable( Class fieldtype, 
					      Object child,
					      Document target, 
					      Map table)
	throws Exception
    {
	if (child == null) {
	    return new Element("null");
	}
	else if (!fieldtype.isPrimitive()) {
	    Element reference = new Element("reference");
	    if (table.containsKey(child)) {
		reference.setText(table.get(child).toString());
	    }
	    else {
		reference.setText( Integer.toString(table.size()) );
		serializeHelper(child, target, table);
	    }
	    return reference;
	}
	else {
	    Element value = new Element("value");
	    value.setText(child.toString());
	    return value;
	}
    }
//stop extract serializeVariable


//start extract deserializeObject

    public static Object deserializeObject( Document source )
	throws Exception 
    {
	List objList = source.getRootElement().getChildren();

	Map table = new HashMap();

	createInstances( table, objList );

	assignFieldValues( table, objList );

	return table.get("0");
    }
//stop extract deserializeObject


//start extract createInstances

    private static void createInstances( Map table, List objList )
	throws Exception
    {
	for (int i = 0; i < objList.size(); i++) {
	    Element oElt = (Element) objList.get(i);
	    Class cls = Class.forName(oElt.getAttributeValue("class"));
	    Object instance = null;
	    if (!cls.isArray()) {
		Constructor c = cls.getDeclaredConstructor(null);
		if (!Modifier.isPublic(c.getModifiers())) {
		    c.setAccessible(true);
		}
		instance = c.newInstance(null);
	    }
	    else {
		instance =
		    Array.newInstance(
			   cls.getComponentType(),
			   Integer.parseInt(oElt.getAttributeValue("length")));
	    }
	    table.put(oElt.getAttributeValue("id"), instance);
	}
    }
//stop extract createInstances


//start extract assignFieldValues

    private static void assignFieldValues( Map table, List objList )
        throws Exception
    {
        for (int i = 0; i < objList.size(); i++) {
            Element oElt = (Element) objList.get(i);
            Object instance = table.get( oElt.getAttributeValue("id") );
            List fElts = oElt.getChildren();
            if (!instance.getClass().isArray()) {
                for (int j=0; j<fElts.size(); j++) {
                    Element fElt = (Element) fElts.get(j);
                    String className 
                        = fElt.getAttributeValue("declaringclass");
                    Class fieldDC = Class.forName(className);
                    String fieldName = fElt.getAttributeValue("name");
                    Field f = fieldDC.getDeclaredField(fieldName);
                    if (!Modifier.isPublic(f.getModifiers())) {
                        f.setAccessible(true);
                    }
		    
                    Element vElt = (Element) fElt.getChildren().get(0);
                    f.set( instance,
                           deserializeValue( vElt, f.getType(), table ) );
                }
            }
            else {
                Class comptype =
                    instance.getClass().getComponentType();
                for ( int j = 0; j < fElts.size(); j++) {
                    Array.set( instance, j,
                               deserializeValue( (Element)fElts.get(j),
                                                 comptype, table ));
                }
            }
        }
    }
//stop extract assignFieldValues    


//start extract deserializeValue

    private static Object deserializeValue( Element vElt, 
					    Class fieldType, 
					    Map table )
	throws ClassNotFoundException 
    {
	String valtype = vElt.getName();
	if (valtype.equals("null")) {
	    return null;
	}
	else if (valtype.equals("reference")) {
	    return table.get(vElt.getText());
	}
	else {
	    if (fieldType.equals(boolean.class)) {
		if (vElt.getText().equals("true")) {
		    return Boolean.TRUE;
		}
		else {
		    return Boolean.FALSE;
		}
	    }
	    else if (fieldType.equals(byte.class)) {
		return Byte.valueOf(vElt.getText());
	    }
	    else if (fieldType.equals(short.class)) {
		return Short.valueOf(vElt.getText());
	    }
	    else if (fieldType.equals(int.class)) {
		return Integer.valueOf(vElt.getText());
	    }
	    else if (fieldType.equals(long.class)) {
		return Long.valueOf(vElt.getText());
	    }
	    else if (fieldType.equals(float.class)) {
		return Float.valueOf(vElt.getText());
	    }
	    else if (fieldType.equals(double.class)) {
		return Double.valueOf(vElt.getText());
	    }
	    else if (fieldType.equals(char.class)) {
		return new Character(vElt.getText().charAt(0));
	    }
	    else {
		return vElt.getText();
	    }
	}
    }
//stop extract deserializeValue

}
